package utils

import (
	"fmt"
	"os"
	"sync"
	"sync/atomic"
	"time"
)

/**
  1. 时间轮
  2. 定时任务
  3. 执行时会有些误差 比如 1:00:05分执行的任务 可能执行时是1:00:04.780(可能是tick误差导致)
*/

type TimeWheel struct {
	handlecnt    uint32
	slotNum      uint16
	slotIdx      uint16
	slots        []*DLinklist
	rollt        time.Duration
	ticker       *time.Ticker
	cmdChan      chan interface{}
	activeflag   byte
	exec_task_cb func(rec *TWTaskRec)
	cw           sync.WaitGroup
}

func Func_ExecTask_ASync(rec *TWTaskRec) {
	go rec.DoTask()
}

func NewTWEx(rollInterval time.Duration, slotNum uint16) *TimeWheel {
	if rollInterval < minTickerInterval {
		rollInterval = minTickerInterval
	}
	rv := &TimeWheel{
		rollt:        rollInterval,
		ticker:       time.NewTicker(rollInterval),
		cmdChan:      make(chan interface{}),
		exec_task_cb: Func_ExecTask_ASync,
	}
	rv.setSlotNum(1024)
	return rv
}

func NewTW(rollInterval time.Duration) *TimeWheel {
	return NewTWEx(rollInterval, 1024)
}

func (this *TimeWheel) ConfigExecTaskFunc(fn func(rec *TWTaskRec)) {
	this.exec_task_cb = fn
}

func (this *TimeWheel) setSlotNum(n uint16) {
	this.slotNum = n
	this.slots = make([]*DLinklist, n)
	for i := 0; i < int(n); i++ {
		this.slots[i] = NewDlinklist()
	}
	this.slotIdx = 0
}

func (this *TimeWheel) insertTaskSlot(nc, idx int64, twrec *TWTaskRec) {
	if idx == int64(this.slotNum) {
		idx = 0
	}
	twrec.n = nc
	twrec.info_idx = idx
	node := NewDlinknodeFromPool()
	node.Value = twrec
	this.slots[idx].Append(node)
}

func (this *TimeWheel) insertTask(nextTime time.Time, twrec *TWTaskRec) {
	var idx, nc int64
	defer func() {
		if err := recover(); err != nil {
			fmt.Printf("[TimeWheel-insertTask]idx:%d, nc:%d, err:%v\n", idx, nc, err)
		}
	}()
	nowT := time.Now()
	if !nextTime.After(nowT) {
		nextTime1 := nextTime
		nextTime = NextTime(nowT, nowT, this.rollt)
		fmt.Fprintf(os.Stderr,
			"[时间轮]插入任务时间(%s)比当前时(%s)间更早, 自动往后到下一个时间点触发(%s)\r\n",
			DateTimeString3(nextTime1), DateTimeString3(nowT), DateTimeString3(nextTime))
	}

	idx, nc = CalcuTWSlot(int64(this.slotIdx), int64(this.slotNum), nowT, nextTime, this.rollt)
	if idx == int64(this.slotIdx) && nc == 0 { // 和当前时间一样, 插入到下一个槽
		idx++
	}

	//{   // debug
	//	fmt.Printf("[insertTask]now:[%d][%d], next:[%d][%d],rollt:%.2f\r\n", this.slotNum, this.slotIdx, nc, idx, this.rollt.Seconds())
	//	t1 := CalcuTWTrigTime(int64(this.slotIdx), int64(this.slotNum), idx, nc, nowT, this.rollt)
	//	fmt.Printf("[insertTask]now:%s,next:%s, stdnext:%s\r\n\r\n", DateTimeString3(nowT), DateTimeString3(t1), DateTimeString3(nextTime))
	//}

	this.insertTaskSlot(nc, idx, twrec)

	//twrec.n = nc
	//twrec.info_idx = idx
	//node := NewDlinknodeFromPool()
	//node.Value = twrec
	//this.slots[idx].Append(node)
}

func (this *TimeWheel) syncCmd(args ...interface{}) {
	if GoFunCatchException {
		defer PanicHandler()
	}

	cmdId := args[0].(int)
	if cmdId == 0 {
		twrec := &TWTaskRec{
			handle: args[1].(uint32),
			id:     args[2],
			cb:     args[3],
		}
		startTime := args[4].(time.Time)
		if startTime.IsZero() {
			idx := int64(this.slotIdx) + 1
			this.insertTaskSlot(0, idx, twrec)
		} else {
			this.insertTask(startTime, twrec)
		}
		//fmt.Println("idx:", this.slotIdx, "now:", time.Now(), ", next:", startTime, ", nextIdx:", twrec.info_idx, "new_nc:", twrec.n)
	} else if cmdId == 1 { // 定时任务
		interval := args[4].(time.Duration)
		twrec := &TWTaskRec{
			handle: args[1].(uint32),
			id:     args[2],
			cb:     args[3],
		}
		twrec.aftercall = func() {
			if this.activeflag == 1 && twrec.breakflag == 0 {
				next := time.Now().Add(interval)
				this.insertTask(next, twrec)
			}
		}

		if startTime, ok := args[5].(time.Time); ok {
			if TimeIsUnixZero(startTime) {
				this.insertTask(time.Now().Add(interval), twrec)
			} else {
				if startTime.After(time.Now()) {
					this.insertTask(startTime, twrec)
				} else {
					newTime := NextTime(startTime, time.Now(), interval)
					this.insertTask(newTime, twrec)
				}
			}
		} else if after, ok := args[5].(time.Duration); ok {
			t1 := time.Now().Add(this.rollt).Add(after)
			this.insertTask(t1, twrec)
		}
	} else if cmdId == 2 { // 固定时间任务
		interval := args[4].(time.Duration)
		twrec := &TWTaskRec{
			handle: args[1].(uint32),
			id:     args[2],
			cb:     args[3],
		}

		{
			t1 := FixintervalOffsetTime(time.Now().Add(this.rollt), interval, 1)
			//fmt.Println(DateTimeString3(t1))
			this.insertTask(t1, twrec)
		}

		twrec.aftercall = func() {
			if this.activeflag == 1 && twrec.breakflag == 0 {
				var next time.Time
				if this.rollt < time.Second {
					next = FixintervalOffsetTime(time.Now().Add(time.Second), interval, 1)
				} else {
					next = FixintervalOffsetTime(time.Now().Add(this.rollt), interval, 1)
				}
				//fmt.Println(DateTimeString3(next))
				this.insertTask(next, twrec)
			}
		}
	} else if cmdId == 10 { // 根据id删除
		// id, wt
		id := args[1]
		this.checkAllTask(func(rec *TWTaskRec) bool {
			if id == rec.id { // 删除
				return true
			} else {
				return false
			}
		})

		if args[2] != nil {
			wt := args[2].(*sync.WaitGroup)
			if wt != nil {
				wt.Done()
			}
		}
	} else if cmdId == 11 { // 根据handle删除
		// handle, wt
		handle := args[1].(uint32)
		this.checkAllTask(func(rec *TWTaskRec) bool {
			if handle == rec.handle { // 删除
				return true
			} else {
				return false
			}
		})

		if args[2] != nil {
			wt := args[2].(*sync.WaitGroup)
			wt.Done()
		}
	}
}

func (this *TimeWheel) innerPushCmd(args ...interface{}) {
	this.cmdChan <- args
}

func (this *TimeWheel) AddTaskNextSlot(id interface{}, fn func()) (handle uint32) {
	handle = atomic.AddUint32(&this.handlecnt, 1)
	this.innerPushCmd(0, handle, id, fn, ZeroTime)
	return handle
}

func (this *TimeWheel) AddTaskNextTime(id interface{}, fn func(), nextTime time.Time) (handle uint32) {
	handle = atomic.AddUint32(&this.handlecnt, 1)
	this.innerPushCmd(0, handle, id, fn, nextTime)
	return handle
}

func (this *TimeWheel) AddTaskInterval(id interface{}, fn func() bool, interval time.Duration, startTime time.Time) (handle uint32) {
	if interval < this.rollt {
		fmt.Fprintf(os.Stderr, "interval must be > roll time[%s]", this.rollt)
		return
	}
	handle = atomic.AddUint32(&this.handlecnt, 1)
	this.innerPushCmd(1, handle, id, fn, interval, startTime)
	return
}

func (this *TimeWheel) AddTaskAfterInterval(id interface{}, after, interval time.Duration, fn func() bool) (handle uint32) {
	if interval < this.rollt {
		fmt.Fprintf(os.Stderr, "interval must be > roll time[%s]", this.rollt)
		return
	}
	handle = atomic.AddUint32(&this.handlecnt, 1)
	this.innerPushCmd(1, handle, id, fn, interval, after)
	return
}

func (this *TimeWheel) AddTaskFixedInterval(id interface{}, interval time.Duration, fn func() bool) (handle uint32) {
	if interval < time.Second || interval < this.rollt {
		fmt.Fprintf(os.Stderr, "interval must be > roll time[%s] and a second", this.rollt)
		return
	}
	handle = atomic.AddUint32(&this.handlecnt, 1)
	this.innerPushCmd(2, handle, id, fn, interval)
	return
}

/*
*

	dayTime只要时间和上
*/
func (this *TimeWheel) AddTaskEveryDayTime(id interface{}, fn func() bool, dayTime time.Time) (handle uint32) {
	handle = atomic.AddUint32(&this.handlecnt, 1)
	this.innerPushCmd(1, handle, id, fn, time.Hour*24, dayTime)
	return
}

func (this *TimeWheel) RemoveByID(id interface{}, wt *sync.WaitGroup) {
	this.innerPushCmd(10, id, wt)
	return
}

func (this *TimeWheel) RemoveByHandle(handle uint32, wt *sync.WaitGroup) {
	this.innerPushCmd(11, handle, nil)
	return
}

func (this *TimeWheel) RemoveByHandleAndWait(handle uint32) {
	var wt sync.WaitGroup
	wt.Add(1)
	this.innerPushCmd(11, handle, &wt)
	wt.Wait()
	return
}

func (this *TimeWheel) Stop() {
	this.cmdChan <- nil
}

func (this *TimeWheel) WaitFor() {
	this.cw.Wait()
}

func (this *TimeWheel) Start() {
	if GoFunCatchException {
		defer PanicHandler()
	}
	this.activeflag = 1
	this.cw.Add(1)
	defer func() {
		this.activeflag = 0
		this.cw.Done()
	}()

	// 整秒开始
	WaitForStartOfNextSecond()
	//fmt.Println("now:", time.Now())

DONE:
	for {
		select {
		case <-this.ticker.C:
			this.tickHandler()
			// fmt.Println("now:", DateTimeString3(time.Now()))
		case cmdV := <-this.cmdChan:
			if cmdV == nil {
				this.ticker.Stop()
				break DONE
			}
			args := cmdV.([]interface{})
			this.syncCmd(args...)
		}
	}
}

func (this *TimeWheel) tickHandler() {
	// 先转动再执行
	this.slotIdx++
	if this.slotIdx == this.slotNum {
		this.slotIdx = 0
	}

	l := this.slots[this.slotIdx]
	this.scanAndRunTask(l)
}

func (this *TimeWheel) checkAllTask(checkRemoveFunc func(rec *TWTaskRec) bool) {
	for i := 0; i < len(this.slots); i++ {
		l := this.slots[i]
		this.checkSlotItemsRemove(l, checkRemoveFunc)
	}
}

func (this *TimeWheel) execRecTask(rec *TWTaskRec) {
	if this.exec_task_cb != nil {
		this.exec_task_cb(rec)
	} else {
		go rec.DoTask()
	}

	rec.doAfterCall()

}

func (this *TimeWheel) checkSlotItemsRemove(l *DLinklist, checkfn func(rec *TWTaskRec) bool) {
	l.RangeEx(func(idx int, val interface{}, removeit *bool) bool {
		rec := val.(*TWTaskRec)
		if checkfn(rec) {
			*removeit = true // 执行完进行移除
		}
		return true
	}, func(node *DLinkNode) {
		node.release2Pool()
	})
}

func (this *TimeWheel) scanAndRunTask(l *DLinklist) {
	l.RangeEx(func(idx int, val interface{}, removeit *bool) bool {

		rec := val.(*TWTaskRec)
		if rec.n == 0 {
			this.execRecTask(rec)
			*removeit = true // 执行完进行移除
		} else {
			rec.n--
		}
		return true
	}, func(node *DLinkNode) {
		node.release2Pool()
	})

	//
	//for e := l.Front(); e != nil; {
	//	task := e.Value.(Job)
	//	if this.jobUseGo {
	//		GoFunc2(func() {
	//			task()
	//		})
	//	} else {
	//		task()
	//	}
	//	e = e.Next()
	//}
}

func CalcuTWSlot(current_idx, slotN int64, now, next_t time.Time, roll_t time.Duration) (slot_idx, slot_n int64) {
	//fmt.Printf("CalcuTWSlot, %d, %d, [%s], [%s], %.2f\r\n", current_idx, slotN, now, next_t, roll_t.Seconds())
	if now.After(next_t) {
		//next_t = NextTime(next_t, now, interval time.Duration) time.Time
		panic(fmt.Sprintf("next_t(%s) must after now time(%s)", next_t, now))
	}
	t1 := next_t.Sub(now)
	n := int64(t1 / roll_t)                          // 需要转动几次
	abs_slot_no := n + int64(current_idx)            // 下一次的slot_no
	nc := (abs_slot_no - current_idx) / int64(slotN) // 几次
	idx := abs_slot_no % int64(slotN)
	if idx < 0 {
		panic(fmt.Sprintf("idx:%d, n:%d, now:%s, next_t:%s, roll_t:%s", current_idx, slot_n, DateTimeString2(now), DateTimeString2(next_t), roll_t))
	}
	return idx, nc
}

func NextTime(startTime, nowTime time.Time, interval time.Duration) time.Time {
	var span int64
	span0 := (nowTime.UnixNano() - startTime.UnixNano())
	if span0 == 0 { // 时间相等
		return startTime.Add(interval)
	}

	span = span0 / int64(interval) * int64(interval)
	if span0%int64(interval) > 0 {
		span = span + int64(interval)
	}

	newTime := startTime.Add(time.Duration(span))

	//for {
	//	if newTime.After(nowTime) {
	//		break
	//	}
	//	newTime = newTime.Add(interval)
	//}
	return newTime
}

func WaitForStartOfNextSecond() {

	for {
		startT := FixintervalOffsetTime(time.Now(), time.Second, 1)
		d := startT.Sub(time.Now())
		if d > 0 {
			waitT := time.NewTicker(d)
			<-waitT.C
			break
		}
	}

}

func CalcuTWTrigTime(current_idx, slotN, taskIdx, taskN int64, now time.Time, roll_t time.Duration) time.Time {
	var idx, _taskN int64
	_taskN = taskN
	idx = current_idx
	rval := now
	for {
		rval = rval.Add(roll_t)
		idx++
		if idx == slotN {
			idx = 0
		}

		if idx == taskIdx {
			if _taskN == 0 {
				return rval
			} else {
				_taskN--
			}
		}

	}

}
